package avriServer.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.Calendar;
import java.util.HashMap;

import avriServer.command.Command;
import avriServer.command.CommandBuffer;
import avriServer.command.WaiterBuilder;
import avriServer.json.JSONArray;
import avriServer.json.JSONException;
import avriServer.json.JSONObject;
import avriServer.json.JSONStringer;
import avriServer.server.Dispatcher;
import avriServer.server.AvriServer;
import avriServer.util.Input;


public class Worker extends Client implements Runnable {
	private HashMap<String, String> variable = new HashMap<String, String>();
	private CommandBuffer resultcommands = new CommandBuffer();
	private boolean hasCreatedAWaiter = false ;
	/**
	 * Constructor.
	 * @param s
	 */
	public Worker(Socket _socket){
		super(_socket) ;
	}

	public void run () {
		//define the inputStream
		InputStream is;
		try {
	        this.printStream = new PrintStream(this.socket.getOutputStream());
	        is = this.socket.getInputStream();
			this.bufferedReader = new BufferedReader(new InputStreamReader(is));
			this.setVariable();
			//Client request 
			//e.g: commands=[{command: "connection", attributes: {attribute1: "value1", attribute2: "value2"}}, {command: "subscribe", attributes: {attribute1: "value1", attribute2: "value2"}}]
			String commands = this.variable.get("commands");
            //commands is obligatory
			if (commands == null) {
				this.sendAnswer("avriServer.error", "no commands");
            	this.close();
			}
			else {
	        	commands = Input.DECODE(commands);
	        	System.out.println(commands);
	        	JSONArray listCommands = new JSONArray(commands);
	        	JSONObject aJsonCommand ;
	        	//We list all the commands and execute them
	        	for (int i=0;i <listCommands.length();i++) {
	        		aJsonCommand = listCommands.getJSONObject(i);        		
	        		//Execute the command.
	        		//We build the command using reflectivity API :
	        		//System.out.println("avriChat.command." + this.capitalizeClass(aJsonCommand.getString("command")));
	                Class<?> myClass = Class.forName(Dispatcher.getServer().getCommandPackageName()+this.capitalizeClass(aJsonCommand.getString("command")));
	                //We create the new object
	                //System.out.println(maClasse);
	                Constructor<?> construct = myClass.getConstructor(JSONObject.class, HashMap.class);
	                //publicId isn't defined and it's an WaiterBuilder
	                boolean asGenerateAnId = false ;
	    			if (this.variable.get("publicId") == null || this.variable.get("publicId").equals("")) {
	    				String n = Dispatcher.generateUniqueWaiterPublicId();
	    				this.variable.put("publicId", n);
	    				asGenerateAnId = true ;
	    			}
	                Command theCommand = (Command) construct.newInstance(aJsonCommand.getJSONObject("attributes"), this.variable);
	                //session is necessary except for WaiterBuilder commands
	                if (!(theCommand instanceof WaiterBuilder) && this.variable.get("session") == null) {
	    				this.sendAnswer("avriServer.error", "no session");
	                	this.close();
	                }
	                //publicId is obligatory && command != WaiterBuilder
	    			else if (!(theCommand instanceof WaiterBuilder) && asGenerateAnId) {
	    				this.sendAnswer("avriServer.error", "PublicId is obligatory, except for WaiterBuilder commands");
	                	this.close();
	    			}
	    			else {
		                theCommand.execute(this);
		                //if the command is a WaiterBuilder, it means that we musn't close the connection : it will be used by the waiter to push data.
		                //otherwise it means that client has just sent a simple order and doesn't build any Waiter.
		                //so we send the resultCommand info to the client and close the connection.
		                if (!(theCommand instanceof WaiterBuilder)) {
		                	CommandBuffer _resultCommands = theCommand.getResultCommandBuffer() ;
		                	if (_resultCommands.getNumberOfCommand()> 0)
		                		this.addResultcommands(_resultCommands);
		                }else {
		                	this.hasCreatedAWaiter = true;
		                }
	                }
	        	}
            	this.sendAnswer(this.getResultCommands());
            	if (!hasCreatedAWaiter) 
            		this.close();
        	}
		} catch (IOException e1) {
			//if something is wrong, we have to close the connection
			e1.printStackTrace();
			this.close();
		} catch (JSONException e2) {
			this.sendAnswer("avriServer.error", "malformed command");
			this.close();
		} catch (ClassNotFoundException e3) {
			this.sendAnswer("avriServer.error", Command.ERROR_UNEXISTANT_COMMAND+": "+e3);
			this.close();
		} catch (NoClassDefFoundError e4) {
			this.sendAnswer("avriServer.error", Command.ERROR_UNEXISTANT_COMMAND+": "+e4);
			this.close();
		} catch (InvocationTargetException e5) {
			//if something is wrong, we have to close the connection
			this.sendAnswer("avriServer.disconnect", "Unexistant waiter: "+e5+", Error UnvalidWaiterException probably throws into the Waiter Constructor");
			this.close();
		}
		catch (Exception e6) {
			//if something is wrong, we have to close the connection
			e6.printStackTrace();
			this.sendAnswer("avriServer.disconnect", "Server intern error: "+e6);
			this.close();
		}
	}
	
	/**
	 * add a result command to the list 
	 * @param _resultCommands
	 */
	private void addResultcommands (CommandBuffer _resultCommands) {
		this.resultcommands.append(_resultCommands);
	}
	
	/**
	 * @return the result command as an JSON Array of command object collected on each command.
	 */
	private String getResultCommands () {
		return this.resultcommands.getCommands();
	}
	/**
	 * Capitalize the last part of the _s String
	 * e.g: capitalizeClass("chat.say") return "chat.Say"
	 * @param _s
	 * @return _s capitalized
	 */
	private String capitalizeClass (String _s) {
		 if (_s == null) {
	             return null;
	     }
	     if (_s.length() == 0) {
	             return _s;
	     }
	     StringBuffer result = new StringBuffer(_s);
	     int indexOfDot = _s.lastIndexOf('.') ;
	     if (indexOfDot == -1) 
	    	 return result.replace(0, 1, result.substring(0, 1).toUpperCase()).toString();
    	 return result.replace(indexOfDot+1, indexOfDot+2, result.substring(indexOfDot+1, indexOfDot+2).toUpperCase()).toString();
	}
	/**
	 * Parse the GET variables of the http request.
	 * e.g: server?session=123&mavar=foo will build the this.variable hashMap: this.variable {"session" => "123", "mavar" => "foo"}
	 * @throws IOException
	 */
	private void setVariable() throws IOException{
		String line = this.bufferedReader.readLine();
		System.out.println(line);
		int openName = line.indexOf('?',0)+1;
		int closeName = line.indexOf('=',openName);
		int closeValue = line.indexOf('&',closeName);
		if(closeValue ==-1)
			closeValue=line.indexOf(' ',closeName);
		while(closeName != -1){	
			this.variable.put(line.substring(openName, closeName),line.substring(closeName+1, closeValue));
			openName = closeValue +1;
			closeName = line.indexOf('=',closeValue+1);
			closeValue = line.indexOf('&',closeName);
			if(closeValue ==-1)
				closeValue=line.indexOf(' ',closeName);
		}
	}
	
	/**
	 * Send the whole answer (_answer + header).
	 * Content-type is an application/json
	 * This method should be used by a Command to send the command's result.
	 * @param _answer the answer 
	 */
	public void sendAnswer (String _functionName, Object... _functionArguments) {
		this.sendAnswer(CommandBuffer.getCommand(_functionName, _functionArguments));
	}
	public void sendAnswer (String _answer) {
		System.out.println(_answer);		
		//Comet opening connection, need a specific answer
		//happen when we have an error during the connection for exemple.
		if (this.hasCreatedAWaiter) {
			this.push(_answer, Integer.parseInt(this.variable.get("cometType")), this.variable.get("cometName"));
		}
		//normal answer for normal command
		else {
			Calendar c = Calendar.getInstance();
			String date = c.get(Calendar.DAY_OF_MONTH) + "/" + c.get(Calendar.MONTH) + "/" + c.get(Calendar.YEAR);
			
			try {
				this.printStream.print("HTTP/1.1 200 OK");
				this.printStream.write(AvriServer.EOL);
				this.printStream.print("Date: "+date);
				this.printStream.write(AvriServer.EOL);
				this.printStream.print("Server: DWR-Reverse-Ajax Comet AvriChatServer");
				this.printStream.write(AvriServer.EOL);
				this.printStream.print("Content-Type: application/json");
				this.printStream.write(AvriServer.EOL);
				this.printStream.print("Cache-Control: no-store, no-cache");
				this.printStream.write(AvriServer.EOL);
				this.printStream.print("Content-Length: "+_answer.length());
				this.printStream.write(AvriServer.EOL);
				this.printStream.write(AvriServer.EOL);
				this.printStream.print(_answer);
				this.printStream.flush();
			} catch (IOException e) {
			}
		}
	}	
}
